package com.emeraldparser.collect;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;

public class HashGraph<V, E> implements Graph<V, E> {

	private final Set<V> vertices = Sets.newHashSet();
	private final UnboundEdges edges;
	private final Map<V, EdgeMultiset<V, E>> edgesBySource = Maps.newHashMap();
	private final Map<V, EdgeMultiset<V, E>> edgesByDestination = Maps.newHashMap();

	public HashGraph() {
		ListenableMultiset<Edge<V, E>> rawEdges = ListenableMultiset.create(HashMultiset.<Edge<V, E>>create())
			/*
			 * A listener that ensures all edge vertices are part of the Set<V> returned by the vertices() method
			 */
			.withListener(new MultisetListener<Edge<V, E>>() {
				@Override
				public void elementsAdded(Edge<V, E> edge, int occurrences) {
					vertices.add(edge.getSource());
					vertices.add(edge.getDestination());
				}
				
				@Override
				public void elementsRemoved(Edge<V, E> element, int occurrences) {}
				
			})
			
			/*
			 * A listener that ensures edges are copied into their respective EdgeMultiset<V, E> within edgesBySource.
			 */
			.withListener(new MultisetListener<Edge<V, E>>() {
				@Override
				public void elementsAdded(Edge<V, E> edge, int occurrences) {
					edgesFrom(edge.getSource()).add(edge, occurrences);
				}
				
				@Override
				public void elementsRemoved(Edge<V, E> edge, int occurrences) {
					edgesFrom(edge.getSource()).remove(edge, occurrences);
				}
			})
			
			/*
			 * A listener that ensures edges are copied into their respective EdgeMultiset<V, E> within edgesByDestination.
			 */
			.withListener(new MultisetListener<Edge<V, E>>() {
				@Override
				public void elementsAdded(Edge<V, E> edge, int occurrences) {
					edgesTo(edge.getDestination()).add(edge, occurrences);
				}
				
				@Override
				public void elementsRemoved(Edge<V, E> edge, int occurrences) {
					edgesTo(edge.getDestination()).remove(edge, occurrences);
				}
			});
		
		this.edges = new UnboundEdges(rawEdges);
	}

	private EdgeMultiset<V, E> edgesFrom(V source) {
		EdgeMultiset<V, E> edges = edgesBySource.get(source);
		if (edges == null) {
			edges = new SourceBoundEdges(HashMultiset.<Edge<V, E>>create(), source);
			edgesBySource.put(source, edges);
		}
		return edges;
	}

	private EdgeMultiset<V, E> edgesTo(V destination) {
		EdgeMultiset<V, E> edges = edgesByDestination.get(destination);
		if (edges == null) {
			edges = new DestinationBoundEdges(HashMultiset.<Edge<V, E>>create(), destination);
			edgesByDestination.put(destination, edges);
		}
		return edges;
	}

	public static <V, E> Graph<V, E> create() {
		return new HashGraph<V, E>();
	}

	@Override
	public Set<V> vertices() {
		// TODO Auto-generated method stub
		return vertices;
	}

	@Override
	public EdgeMultiset<V, E> edges() {
		return edges;
	}

	private class Edges extends AbstractEdgeMultiset<V, E> implements EdgeMultiset<V, E> {

		private final Multiset<Edge<V, E>> delegate;

		public Edges(Multiset<Edge<V, E>> delegate) {
			Preconditions.checkNotNull(delegate, "Delegate must not be null");
			this.delegate = delegate;
		}

		@Override
		protected Multiset<Edge<V, E>> delegate() {
			return delegate;
		}

		@Override
		protected Edges newEdgeMultiset(Multiset<Edge<V, E>> delegate) {
			// TODO Auto-generated method stub
			return new Edges(delegate);
		}

		@Override
		public int add(Edge<V, E> edge, int occurrences) {
			// TODO Auto-generated method stub
			int oldCount = super.add(edge, occurrences);
			vertices.add(edge.getSource());
			vertices.add(edge.getDestination());
			return oldCount;
		}

		@Override
		public boolean add(Edge<V, E> edge) {
			// TODO Auto-generated method stub
			boolean modified = super.add(edge);
			vertices.add(edge.getSource());
			vertices.add(edge.getDestination());
			return modified;
		}

		@Override
		public boolean addAll(Collection<? extends Edge<V, E>> edges) {
			// TODO Auto-generated method stub
			boolean modified = super.addAll(edges);
			for (Edge<V, E> edge : edges) {
				vertices.add(edge.getSource());
				vertices.add(edge.getDestination());
			}
			return modified;
		}

	}

	private class UnboundEdges extends Edges {

		public UnboundEdges(Multiset<Edge<V, E>> delegate) {
			super(delegate);
		}

		@Override
		public EdgeMultiset<V, E> from(V source) {
			// TODO Auto-generated method stub
			// FIXME
			EdgeMultiset<V, E> delegate = edgesBySource.get(source);
			if (delegate == null) {
				delegate = new SourceBoundEdges(delegate, source);
				edgesBySource.put(source, delegate);
			}
			return delegate;
		}

		@Override
		public EdgeMultiset<V, E> to(V destination) {
			// TODO Auto-generated method stub
			// FIXME
			return new DestinationBoundEdges(edgesByDestination.get(destination), destination);
		}

	}

	private class SourceBoundEdges extends Edges {

		private final V source;

		public SourceBoundEdges(Multiset<Edge<V, E>> delegate, V source) {
			super(delegate);
			this.source = source;
		}

		@Override
		public EdgeMultiset<V, E> to(V destination) {
			// TODO Auto-generated method stub
			return new SourceAndDestinationBoundEdges(super.to(destination), source, destination);
		}

	}

	private class DestinationBoundEdges extends Edges {

		private final V destination;

		public DestinationBoundEdges(Multiset<Edge<V, E>> delegate, V destination) {
			super(delegate);
			this.destination = destination;
		}

		@Override
		public EdgeMultiset<V, E> from(V source) {
			// TODO Auto-generated method stub
			return new SourceAndDestinationBoundEdges(super.from(source), source, destination);
		}

	}

	private class SourceAndDestinationBoundEdges extends Edges {

		private final V source;
		private final V destination;

		public SourceAndDestinationBoundEdges(Multiset<Edge<V, E>> delegate, V source, V destination) {
			super(delegate);
			this.source = source;
			this.destination = destination;
		}

		@Override
		public EdgeMultiset<V, E> from(V source) {
			// TODO Auto-generated method stub
			return new SourceAndDestinationBoundEdges(super.from(source), source, destination);
		}

		@Override
		public EdgeMultiset<V, E> to(V destination) {
			// TODO Auto-generated method stub
			return new SourceAndDestinationBoundEdges(super.to(source), source, destination);
		}

		private Multiset<E> labels;

		@Override
		public Multiset<E> labels() {
			// TODO Auto-generated method stub
			if (labels == null) {
				labels = newLabels();
			}
			return labels;
		}

		private Multiset<E> newLabels() {
			return new OneToOneTransformedMultiset<Edge<V, E>, E>() {

				@Override
				public Multiset<Edge<V, E>> sourceMultiset() {
					return SourceAndDestinationBoundEdges.this.delegate();
				}

				@Override
				protected E apply(Edge<V, E> edge) {
					return edge.getLabel();
				}

				@Override
				protected Edge<V, E> unapply(E label) {
					return ImmutableEdge.create(source, destination, label);
				}

			};
		}

	}

}
